<?php

namespace common\business\excel;

use common\business\task\TaskResponseData;
use common\consts\CommonResources;
use common\consts\Consts;
use common\helpers\StringUtil;
use common\models\File;
use Yii;
use Exception;
use PHPExcel;
use PHPExcel_IOFactory;
use PHPExcel_Worksheet;
use PHPExcel_CachedObjectStorageFactory;
use PHPExcel_Settings;
use yii\helpers\Json;

/**
 * Excel导入 抽象类 ImportHandler
 *
 * @package common\business\excel
 */
abstract class ImportHandler
{
    protected $processMessage;              // 处理信息
    protected $successCount;                // 成功总条数
    protected $rows_error;                  // 失败总条数
//    protected $totalAmount;                 // 总金额
    protected $rows_insert;                 // 插入总条数
    protected $rows_cover;                  // 覆盖总条数
    protected $rows_skip;                   // 跳过总条数

    protected $startTime;                  // 处理开始时间
    protected $endTime;                    // 处理结速时间
    protected $file_save_path;                        // 文件存放目录
    protected $file_id;                     // 文件id
    protected $tpl_id;                      // 模板id
    protected $start_row         = 2;       // 开始读取行号
    protected $chunk_size        = 10000;   // 块大小
    protected $filename;                    // 文件名
    protected $_errors           = [];      // 错误
    protected $read_chunk_count;            // 读取块次数
    protected $old_file_id;                 // 原文件id
    protected $use_config;                  // 是否使用配置文件
    protected $_bank_temp_config = null;    // 银行模板配置

    public function __construct($file_id = null, $use_config = false, $model = null)
    {
        $this->use_config = $use_config;
        $this->model = $model;

        if (!StringUtil::isNullOrWhiteSpace($file_id)) {
            $this->beforeProcess($file_id);
        }
    }

    /**
     * 预设置
     *
     * @param $file_id
     */
    protected function beforeProcess($file_id)
    {
        $this->startTime       = time();
        $this->successCount     = 0;
        $this->rows_error       = 0;
        $this->totalAmount      = 0;
        $this->rows_insert      = 0;
        $this->rows_cover       = 0;
        $this->rows_skip        = 0;
        $this->file_id          = $file_id;
        $this->read_chunk_count = 0;
        $this->file_save_path   = Yii::getAlias(Consts::BACKEND_FILE_UPLOAD_PATH);
    }

    /**
     * 结束执行
     */
    public function afterProcess()
    {
//        $this->endTime = time();
//        // 修改文件处理状态
//        $model                  = BankRunningFiles::findOne($this->file_id);
//        $model->status          = BankConstants::RUNNING_FILES_STATUS_DONE;
//        $model->rows_insert     = $this->rows_insert;
//        $model->rows_cover      = $this->rows_cover;
//        $model->rows_skip       = $this->rows_skip;
//        $model->rows_error      = $this->rows_error;
//        $model->process_message = $this->getProcessMessage();
//        $model->save();
//
//        if ($this->old_file_id) {
//            $om                  = BankRunningFiles::findOne($this->old_file_id);
//            $om->process_message .= '除已确认数据，其余已被文件ID(' . $this->file_id . ')所覆盖;';
//            $om->save();
//        }
    }

    /**
     * excel导入处理
     *
     * @param PHPExcel           $objExcel
     * @param PHPExcel_Worksheet $currentSheet
     * @param int                $startRow
     * @param  int               $highestRows
     */
    abstract public function process(&$objExcel, &$currentSheet, $startRow, $highestRows);

    /**
     * 执行
     */
    public function execute()
    {
        $fileModel = File::findOne($this->file_id);

        if ($this->validate($fileModel)) {

            $fileModel->file_status = Consts::FILE_STATUS_IN_PROCESS;
            if (!$fileModel->save()){
                $failReason = Json::encode($fileModel->getErrors());
                return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_NAME_MODEL_FAIL('Execute', 'File', $fileModel->file_name, $failReason));
            }

            //$this->clearErrors();
            try {
                ini_set("memory_limit", '-1');
                set_time_limit(0);
                $this->readExcel();
                $this->afterProcess();
            } catch (Exception $e) {
                $this->addError('process', $e->getMessage());
                $this->handleError();

                //var_dump($e);
                return false;
            }

            return true;
        }

        return false;
    }

    public function readExcel()
    {
        $cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_discISAM;
        PHPExcel_Settings::setCacheStorageMethod($cacheMethod);
        $file = $this->filename;

        $objReader = null;

        if (isset($pathinfo['extension']) && strtolower($pathinfo['extension']) == 'csv') {
            $objReader = PHPExcel_IOFactory::createReader("csv");
        } else {
            $readerType = PHPExcel_IOFactory::identify($file);
            $objReader  = PHPExcel_IOFactory::createReader($readerType);
        }

        // 分块读excel
        // 开始读取行
        $startRow = $this->start_row;
        if ($this->use_config) {
            $tempConfig = $this->getBankTempConfig();
            if (!isset($tempConfig)) {
                throw new Exception("银行配置模板为空");
            }
            $startRow = $tempConfig->row_index;
        }

        $chunkSize = $this->chunk_size;

        $objReader   = $objReader->setReadDataOnly(true);
//        $chunkFilter = new ChunkReadFilter();
//        while (true) {
//            $this->read_chunk_count += 1;
//            echo '\n开始load块' . $this->read_chunk_count . "次---";
//            $st = time();
//            $chunkFilter->setRows($startRow, $chunkSize);
//            $objReader = $objReader->setReadFilter($chunkFilter); // 设置实例化的过滤器对象
//            echo "\n块大小:$chunkSize,范围:$startRow-" . ($startRow + $chunkSize);
//            $objExcel = $objReader->load($file);
//            $et       = time();
//            echo "\n第$this->read_chunk_count 次load完成用时：" . Dh::useTimeToStr($et - $st);
//            // 开始读取每行数据，并插入到数据库
//            $currentSheet = $objExcel->getSheet(0);//读取第一个工作表
//            $highestRows  = $currentSheet->getHighestRow();//取得最大的行号
//            $st           = time();
//            $this->process($objExcel, $currentSheet, $startRow, $highestRows);
//            echo "\n第$this->read_chunk_count 次process用时：" . Dh::useTimeToStr(time() - $st);
//            if ($highestRows == 1) {
//                break; // 退出读块
//            }
//            $startRow += $chunkSize;
//        }

        $objExcel = $objReader->load($file);
        // 开始读取每行数据，并插入到数据库
        $currentSheet = $objExcel->getSheet(0);//读取第一个工作表
        $highestRows  = $currentSheet->getHighestRow();//取得最大的行号
        $this->process($objExcel, $currentSheet, $startRow, $highestRows);
    }

    /**
     * 获取 银行模板配置
     *
     * @return BankTempConfig
     */
    public function getBankTempConfig()
    {
        if (is_null($this->_bank_temp_config)) {
            $this->_bank_temp_config = BankTempConfig::findOne($this->tpl_id);
        }

        return $this->_bank_temp_config;
    }

    /**
     * @param $fileModel File
     *
     * @return bool
     */
    public function validate($fileModel)
    {
        if (!$fileModel) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Validate', 'File', 'File not found in DB');
            $this->addError('validate', $errMsg);
            return false;
        }
        if ($fileModel->status == Consts::FILE_STATUS_FINISHED) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Validate', 'File', 'File Status is Finished');
            $this->addError('validate', $errMsg);
            return false;
        }
        if ($fileModel->status == Consts::FILE_STATUS_IN_PROCESS) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Validate', 'File', 'File Status is Processing');
            $this->addError('validate', $errMsg);
            return false;
        }
        if ($fileModel->status == Consts::FILE_STATUS_SYNCHRONIZING) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Validate', 'File', 'File Status is Synchronizing');
            $this->addError('validate', $errMsg);
            return false;
        }

        $fileName4check = iconv('utf-8', 'GBK//TRANSLIT//IGNORE', $fileModel->file_name);

        $this->filename = $fileModel->file_path.$fileName4check;

        if (!is_file($this->filename)) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Validate', 'File', 'File Not Exist on Server');
            $this->addError('validate', $errMsg);
            return false;
        }

        return true;
    }

    public function getUseTimeStr()
    {
        $t = $this->endTime - $this->startTime;

        return Dh::useTimeToStr($t);
    }

    /**
     * 获取错误
     *
     * @param bool $isArray
     *
     * @return array|string
     */
    public function getErrors($isArray = false)
    {
        if ($isArray) {
            return $this->_errors;
        } else {
            return is_array($this->_errors) ? implode(',', $this->_errors) : $this->_errors;
        }
    }

    public function clearErrors()
    {
        return $this->_errors = [];
    }

    /**
     * @param $key
     * @param $error
     */
    public function addError($key, $error)
    {
        $this->_errors[$key] = $error;
    }

    public function getProcessMessage()
    {
        $proc_msg = date('Y-m-d H:i:s', $this->startTime) . '开始导入文件,成功导入' . ($this->rows_insert + $this->rows_cover) .
            '条记录,总金额:' . Mh::format($this->totalAmount, 2) .
            ';其中新增' . $this->rows_insert . '条';
        if ($this->rows_cover) {
            $proc_msg .= ',覆盖数据' . $this->rows_cover . '条';
        }
        if ($this->rows_skip) {
            $proc_msg .= ';跳过数据' . $this->rows_skip . '条';
        }
        if ($this->rows_error) {
            $proc_msg .= ';失败' . $this->rows_error . '条';
        }
        $proc_msg .= ';用时:' . $this->getUseTimeStr() . ";";

        return $proc_msg;
    }


    /**
     * 处理出错
     * @return TaskResponseData
     */
    public function handleError()
    {
        $fileInDb = File::findOne($this->file_id);

        if (!$fileInDb){
            $errMsg = CommonResources::ACTION_FAIL_MSG('Handle Error', 'File','File not found');
            return new TaskResponseData(TaskResponseData::CODE_ERROR, $errMsg);
        }

        $fileInDb->file_status = Consts::FILE_STATUS_FINISHED;
        $fileInDb->file_result_code = Consts::FILE_RESULT_CODE_FAIL;

        if (!$fileInDb->save()) {
            $failReason = Json::encode($fileInDb->getErrors());
            return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_NAME_MODEL_FAIL('Handle Error', 'File', $fileInDb->file_name, $failReason));
        }

        return new TaskResponseData(TaskResponseData::CODE_SUCCESS, CommonResources::ACTION_NAME_MODEL_SUCCESS('Handle Error', 'File', $fileInDb->file_name));
    }
}
